{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "collapsed": true
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "\n",
       "    <div class=\"bk-root\">\n",
       "        <a href=\"https://bokeh.pydata.org\" target=\"_blank\" class=\"bk-logo bk-logo-small bk-logo-notebook\"></a>\n",
       "        <span id=\"1001\">Loading BokehJS ...</span>\n",
       "    </div>"
      ],
      "text/plain": [
       "\n",
       "    <div class=\"bk-root\">\n",
       "        <a href=\"https://bokeh.pydata.org\" target=\"_blank\" class=\"bk-logo bk-logo-small bk-logo-notebook\"></a>\n",
       "        <span id=\"1001\">Loading BokehJS ...</span>\n",
       "    </div>"
      ]
     },
     "execution_count": 0,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "text/html": [
       "\n",
       "    <div class=\"bk-root\">\n",
       "        <a href=\"https://bokeh.pydata.org\" target=\"_blank\" class=\"bk-logo bk-logo-small bk-logo-notebook\"></a>\n",
       "        <span id=\"1001\">Loading BokehJS ...</span>\n",
       "    </div>"
      ],
      "text/plain": [
       "\n",
       "    <div class=\"bk-root\">\n",
       "        <a href=\"https://bokeh.pydata.org\" target=\"_blank\" class=\"bk-logo bk-logo-small bk-logo-notebook\"></a>\n",
       "        <span id=\"1001\">Loading BokehJS ...</span>\n",
       "    </div>"
      ]
     },
     "execution_count": 0,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import bokeh.plotting as bk\n",
    "from bokeh.io import output_notebook\n",
    "output_notebook()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "import sys\n",
    "sys.path.append('../') # go to parent dir\n",
    "import numpy as np\n",
    "np.set_printoptions(precision=3, formatter={'float': '{: 0.3f}'.format})\n",
    "np.seterr('warn')\n",
    "import os\n",
    "import cProfile, pstats, io"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "from bokeh.palettes import Category20\n",
    "colors = Category20[20]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Load Data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "path = os.path.join(os.getcwd()+'/ETT_TOOLBOX/example/', 'data')\n",
    "frame_start, frame_end = -1, 100\n",
    "measurements = np.load(os.path.join(path, 'simulated_data' + '.npy'))\n",
    "gt_bboxes = np.load(os.path.join(path, 'gt_bboxes' + '.npy'))\n",
    "if frame_end is -1:\n",
    "    measurements = measurements[measurements['ts'] >= frame_start]\n",
    "    gt_bboxes = gt_bboxes[gt_bboxes['ts'] >= frame_start]\n",
    "else:\n",
    "    measurements = measurements[ (measurements['ts'] >= frame_start) & (measurements['ts'] <= frame_end)]\n",
    "    gt_bboxes = gt_bboxes[ (gt_bboxes['ts'] >= frame_start) & (gt_bboxes['ts'] <= frame_end)]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Gamma Gaussian Inverse Wishart Tracker"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Create Tracker"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [],
   "source": [
    "steps = max(measurements['ts']) + 1\n",
    "\n",
    "# tracker config\n",
    "dt = 0.04\n",
    "sa_sq = 1.5 ** 2\n",
    "\n",
    "config = {\n",
    "    'steps': steps + 1,\n",
    "    'd': 2,\n",
    "    's': 2,\n",
    "    'sd': 4,\n",
    "    'lambda_d': 0.999,\n",
    "    'eta': 1.0 / 0.999,\n",
    "    'n_factor': 1.0,\n",
    "    'f': np.asarray([[1, dt], [0, 1]], dtype='f4'),\n",
    "    'q': sa_sq * np.outer(np.asarray([dt ** 2 / 2.0, dt]), np.asarray([dt ** 2 / 2.0, dt])),\n",
    "    'h': np.asarray([[1, 0]], dtype='f4'),\n",
    "    'init_m': [6.5, 2.5, 12, 0],\n",
    "    'init_c': 2 ** 2 * sa_sq * np.outer(np.asarray([dt ** 2 / 2.0, dt]), np.asarray([dt ** 2 / 2.0, dt])),\n",
    "    'init_alpha': 5,\n",
    "    'init_beta': 1,\n",
    "    'init_v': np.diag([4, 2]),\n",
    "    'init_nu': 5,\n",
    "}\n",
    "import sys\n",
    "sys.path.insert(0,'/Users/yizhou/code/MKCF_MAC_Python3.6/source/ETT_TOOLBOX/')\n",
    "from models import GgiwTracker as Tracker"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Run Tracker"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "total time: 0.254554, total steps: 97, average step time: 0.002624\n",
      "         21000 function calls in 0.255 seconds\n",
      "\n",
      "   Ordered by: cumulative time\n",
      "   List reduced from 69 to 10 due to restriction <10>\n",
      "\n",
      "   ncalls  tottime  percall  cumtime  percall filename:lineno(function)\n",
      "      100    0.001    0.000    0.255    0.003 ../tracker.py:47(step)\n",
      "      100    0.024    0.000    0.136    0.001 ../models/ggiw.py:152(correct)\n",
      "      100    0.010    0.000    0.118    0.001 ../models/ggiw.py:132(predict)\n",
      "      400    0.110    0.000    0.110    0.000 {built-in method numpy.core.multiarray.dot}\n",
      "      200    0.066    0.000    0.070    0.000 /Users/Jens/anaconda3/lib/python3.6/site-packages/numpy/linalg/linalg.py:1737(slogdet)\n",
      "      100    0.002    0.000    0.021    0.000 /Users/Jens/anaconda3/lib/python3.6/site-packages/numpy/core/einsumfunc.py:819(einsum)\n",
      "      200    0.007    0.000    0.015    0.000 /Users/Jens/anaconda3/lib/python3.6/site-packages/scipy/special/spfun_stats.py:44(multigammaln)\n",
      "      100    0.004    0.000    0.009    0.000 /Users/Jens/anaconda3/lib/python3.6/site-packages/numpy/core/einsumfunc.py:541(einsum_path)\n",
      "      100    0.002    0.000    0.009    0.000 /Users/Jens/anaconda3/lib/python3.6/site-packages/numpy/core/numeric.py:1123(tensordot)\n",
      "      700    0.009    0.000    0.009    0.000 {method 'reduce' of 'numpy.ufunc' objects}\n",
      "\n",
      "\n",
      "\n"
     ]
    }
   ],
   "source": [
    "# tracker definition\n",
    "tracker = Tracker(dt=dt, **config)\n",
    "\n",
    "pr = cProfile.Profile()\n",
    "for i in range(steps):\n",
    "    scan = measurements[measurements['ts'] == i]\n",
    "    pr.enable()\n",
    "    tracker.step(scan)\n",
    "    pr.disable()\n",
    "\n",
    "estimates, log_lik = tracker.extract()\n",
    "bboxes = tracker.extrackt_bbox()\n",
    "\n",
    "s = io.StringIO()\n",
    "ps = pstats.Stats(pr, stream=s).sort_stats('cumulative')\n",
    "ps.print_stats(10)\n",
    "\n",
    "print('total time: {:f}, total steps: {:d}, average step time: {:f}'.format(\n",
    "    ps.total_tt, max(measurements['ts']) - 2, ps.total_tt / (max(measurements['ts']) - 2)))\n",
    "print(s.getvalue())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Calculate Wasserstein Metric"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "from metric import point_set_wasserstein_distance as pt_wsd\n",
    "from misc import convert_rectangle_to_eight_point\n",
    "eight_pts = convert_rectangle_to_eight_point(bboxes[1:])  # drop prior bounding box\n",
    "eight_pts_gt = convert_rectangle_to_eight_point(gt_bboxes)\n",
    "\n",
    "wsd = np.zeros(len(gt_bboxes), dtype='f8')\n",
    "for i, (pts, gt_pts) in enumerate(zip(eight_pts, eight_pts_gt)):\n",
    "    wsd[i] = pt_wsd(pts, gt_pts, p=2.0)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### data source version"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "from bokeh.models import ColumnDataSource\n",
    "\n",
    "stride = 5\n",
    "\n",
    "est_source = ColumnDataSource(\n",
    "        data={\n",
    "            'ts': estimates['ts'],\n",
    "            'm_x': estimates['m'][:, 0],\n",
    "            'm_y': estimates['m'][:, 1],\n",
    "            'v_x': estimates['m'][:, 2],\n",
    "            'v_y': estimates['m'][:, 3],\n",
    "            'nu': estimates['nu'],\n",
    "            'v': estimates['v'],\n",
    "            'v_hat': estimates['v_hat'],\n",
    "            'phi': 0.5 * np.arctan2(2 * estimates['v_hat'][:, 0, 1], estimates['v_hat'][:, 0, 0] - estimates['v_hat'][:, 1, 1]),\n",
    "            'loglik': log_lik,\n",
    "        }\n",
    "    )\n",
    "\n",
    "meas_source = ColumnDataSource(\n",
    "        data={\n",
    "            'ts': measurements['ts'],\n",
    "            'x': measurements['xy'][:, 0],\n",
    "            'y': measurements['xy'][:, 1],\n",
    "        }\n",
    ")\n",
    "\n",
    "sel = measurements['ts'] % stride == 0\n",
    "meas_source_sel = ColumnDataSource(\n",
    "        data={\n",
    "            'ts': measurements['ts'][sel],\n",
    "            'x': measurements['xy'][sel, 0],\n",
    "            'y': measurements['xy'][sel, 1],\n",
    "        }\n",
    ")\n",
    "\n",
    "sel = bboxes['ts'] % stride == 0\n",
    "bbox_source = ColumnDataSource(\n",
    "        data={\n",
    "            'ts': bboxes['ts'][sel],\n",
    "            'm_x': bboxes['center_xy'][sel, 0],\n",
    "            'm_y': bboxes['center_xy'][sel, 1],\n",
    "            'phi': bboxes['orientation'][sel],\n",
    "            'l': bboxes['dimension'][sel, 0],\n",
    "            'w': bboxes['dimension'][sel, 1],\n",
    "        }\n",
    ")\n",
    "\n",
    "gt_bbox_source = ColumnDataSource(\n",
    "        data={\n",
    "            'ts': gt_bboxes['ts'],\n",
    "            'm_x': gt_bboxes['center_xy'][:, 0],\n",
    "            'm_y': gt_bboxes['center_xy'][:, 1],\n",
    "            'phi': gt_bboxes['orientation'],\n",
    "            'l': gt_bboxes['dimension'][:, 0],\n",
    "            'w': gt_bboxes['dimension'][:, 1],\n",
    "            'wsd': wsd,\n",
    "        }\n",
    ")\n",
    "\n",
    "sel = gt_bboxes['ts'] % stride == 0\n",
    "gt_bbox_source_sel = ColumnDataSource(\n",
    "        data={\n",
    "            'ts': gt_bboxes['ts'][sel],\n",
    "            'm_x': gt_bboxes['center_xy'][sel, 0],\n",
    "            'm_y': gt_bboxes['center_xy'][sel, 1],\n",
    "            'phi': gt_bboxes['orientation'][sel],\n",
    "            'l': gt_bboxes['dimension'][sel, 0],\n",
    "            'w': gt_bboxes['dimension'][sel, 1],\n",
    "        }\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "ename": "NameError",
     "evalue": "name 'meas_source' is not defined",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mNameError\u001b[0m                                 Traceback (most recent call last)",
      "\u001b[0;32m<ipython-input-1-acecc1906679>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[1;32m      7\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m      8\u001b[0m \u001b[0mtop\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mfigure\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mtools\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mTOOLS\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mwidth\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m800\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mheight\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m350\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mtitle\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mmatch_aspect\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mTrue\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0moutput_backend\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m\"webgl\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 9\u001b[0;31m \u001b[0mmeas_scatter\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mtop\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcircle\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'x'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m'y'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mcolor\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m'#303030'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mlegend\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m'measurements'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msize\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0malpha\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m0.2\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msource\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mmeas_source\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m     10\u001b[0m \u001b[0mtrack_plot\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mtop\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mline\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'm_x'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m'm_y'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mlegend\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m'track'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msource\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mest_source\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m     11\u001b[0m hover = HoverTool(renderers=[track_plot],\n",
      "\u001b[0;31mNameError\u001b[0m: name 'meas_source' is not defined"
     ],
     "output_type": "error"
    }
   ],
   "source": [
    "from bokeh.layouts import gridplot\n",
    "from bokeh.plotting import figure, output_file, show\n",
    "from bokeh.models import HoverTool, BoxSelectTool, PanTool, BoxZoomTool, WheelZoomTool, UndoTool, RedoTool, ResetTool, SaveTool\n",
    "from numpy.linalg import eigvals\n",
    "\n",
    "TOOLS = [PanTool(), BoxSelectTool(), BoxZoomTool(), WheelZoomTool(), UndoTool(), RedoTool(), ResetTool(), SaveTool()]\n",
    "\n",
    "top = figure(tools=TOOLS, width=800, height=350, title=None, match_aspect=True, output_backend=\"webgl\")\n",
    "meas_scatter = top.circle('x', 'y', color='#303030', legend='measurements', size=1, alpha=0.2, source=meas_source)\n",
    "track_plot = top.line('m_x', 'm_y', legend='track', source=est_source)\n",
    "hover = HoverTool(renderers=[track_plot],\n",
    "        tooltips=[\n",
    "            (\"index\", \"$index\"),\n",
    "            (r\"(x, y, phi, v)\", \"(@m_x, @m_y, @phi, @v)\"),\n",
    "            (\"(length, width)\", \"(@d_x, @d_y)\"),\n",
    "        ]\n",
    "    )\n",
    "top.add_tools(hover)\n",
    "\n",
    "bottom = figure(tools=TOOLS, width=800, height=350, title=None, \n",
    "                x_range=top.x_range, y_range=top.y_range, output_backend=\"webgl\")\n",
    "bottom.circle('x', 'y', color='#303030', legend='measurements', size=1, alpha=0.2, source=meas_source_sel)\n",
    "bottom.rect(x='m_x', y='m_y', width='l', height='w', angle='phi', color=\"#CAB2D6\", \n",
    "            fill_alpha=0.2, source=bbox_source, legend='bounding boxes')\n",
    "bottom.rect(x='m_x', y='m_y', width='l', height='w', angle='phi', color='#98df8a', \n",
    "            fill_alpha=0.2, source=gt_bbox_source_sel, legend='ground truth boxes')\n",
    "\n",
    "sel = estimates['ts'] % stride == 0\n",
    "wh = eigvals(estimates['v_hat'][sel]).T\n",
    "wh = np.sqrt(wh)\n",
    "bottom.ellipse(estimates['m'][sel, 0], estimates['m'][sel, 1], width=3 * wh[0], height=3 * wh[1], \n",
    "               angle=0.5 * np.arctan2(2 * estimates['v_hat'][sel, 0, 1], estimates['v_hat'][sel, 0, 0] - estimates['v_hat'][sel, 1, 1]), \n",
    "               color=colors[0], fill_alpha=0.2, legend='shape')\n",
    "\n",
    "bottom.legend.click_policy=\"hide\"\n",
    "\n",
    "p = gridplot([[top], [bottom]])\n",
    "show(p)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Statistics"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "\n",
       "<div class=\"bk-root\">\n",
       "    <div class=\"bk-plotdiv\" id=\"0888f61a-f1dc-4935-bc94-7a83673bbd75\"></div>\n",
       "</div>"
      ]
     },
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {},
     "metadata": {
      "application/vnd.bokehjs_exec.v0+json": {
       "id": "70bc7d8c-da24-40cb-b435-4fad70ee9154"
      }
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "from bokeh.models.widgets import Panel, Tabs\n",
    "from bokeh.layouts import gridplot, column\n",
    "\n",
    "f_wsd = figure(tools=TOOLS, width=800, height=350, title='Wasserstein Distance')\n",
    "f_log_lik = figure(tools=TOOLS, width=800, height=350, title='Likelihood', x_range=f_wsd.x_range)\n",
    "f_wsd.line('ts', 'wsd', legend='wasserstein distance', source=gt_bbox_source, color=colors[0])\n",
    "f_log_lik.line('ts', 'loglik', legend='log likelihood', source=est_source, color=colors[0])\n",
    "pan1 = Panel(child=column([f_wsd, f_log_lik]), title=\"Likelihood and Wasserstein Distance\")\n",
    "\n",
    "f_position = figure(tools=TOOLS, width=800, height=350, title='Position')\n",
    "f_orientation = figure(tools=TOOLS, width=800, height=350, title='Orientation', x_range=f_position.x_range)\n",
    "f_velocity = figure(tools=TOOLS, width=800, height=350, title='Velocity', x_range=f_position.x_range)\n",
    "f_dimension = figure(tools=TOOLS, width=800, height=350, title='Dimension', x_range=f_position.x_range)\n",
    "f_position.line('ts', 'm_x', legend='x position', source=est_source, color=colors[0])\n",
    "f_position.line('ts', 'm_y', legend='y position', source=est_source, color=colors[2])\n",
    "f_position.line('ts', 'm_x', legend='ground truth x position', source=gt_bbox_source, color=colors[1])\n",
    "f_position.line('ts', 'm_y', legend='ground truth y position', source=gt_bbox_source, color=colors[3])\n",
    "f_orientation.line('ts', 'phi', legend='orientation', source=est_source, color=colors[0])\n",
    "f_orientation.line('ts', 'phi', legend='ground truth orientation', source=gt_bbox_source, color=colors[1])\n",
    "f_velocity.line('ts', 'v_x', legend='x velocity', source=est_source, color=colors[0])\n",
    "f_velocity.line('ts', 'v_y', legend='y velocity', source=est_source, color=colors[2])\n",
    "# f_velocity.line('ts', 'v', legend='ground truth orientation', source=gt_bbox_source, color=colors[3])\n",
    "f_dimension.line('ts', 'l', legend='x dimension scaling', source=bbox_source, color=colors[0])\n",
    "f_dimension.line('ts', 'w', legend='y dimension scaling', source=bbox_source, color=colors[2])\n",
    "f_dimension.line('ts', 'l', legend='ground truth x dimension', source=gt_bbox_source, color=colors[1])\n",
    "f_dimension.line('ts', 'w', legend='ground truth y dimension', source=gt_bbox_source, color=colors[3])\n",
    "pan2 = Panel(child=column([f_position, f_orientation, f_velocity, f_dimension]), title=\"Kinematic and Extent\")\n",
    "\n",
    "for f in [f_wsd, f_log_lik, f_position, f_orientation, f_velocity, f_dimension]:\n",
    "    f.legend.click_policy=\"mute\"\n",
    "\n",
    "tabs = Tabs(tabs=[pan1, pan2])\n",
    "show(tabs)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
